using System;
using System.Collections.Generic;
using System.Text;
using Zsa.Emulation.Memory;

namespace Zsa.Emulation.Processor
{
    public partial class CpuMemoryMap : MemoryMap
    {
        #region Fields
        /// <summary>
        /// The main memory bytes.
        /// </summary>
        private MemoryBankStream _MainMemory;
        /// <summary>
        /// The cart slot.  Need to change to a class that handles all io.
        /// </summary>
        private Cartridge.CartridgeSlot _CartIO;
        /// <summary>
        /// The controller io.
        /// </summary>
        private Input.ControllerIO _ControllerIO;
        /// <summary>
        /// The ppu memory mapper.
        /// </summary>
        private PPU.PpuCpuMemoryMapper _PpuMemoryMapper;
        /// <summary>
        /// The sound controller.
        /// </summary>
        private Sound.SoundController _SoundController;

        /// <summary>
        /// Temp for save ram io.
        /// </summary>
        private MemoryBankStream _SaveRamTemp;

        /// <summary>
        /// Temp for expansion ram io.
        /// </summary>
        private MemoryBankStream ExpansionTemp;
        /// <summary>
        /// The main memory buffer.
        /// </summary>
        private byte[] _MainMemoryBuffer;

        /// <summary>
        /// True if a dma occured.
        /// </summary>
        private bool _HasDma;
        #endregion Fields


        #region Constructor
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="slot"></param>
        public CpuMemoryMap(Cartridge.CartridgeSlot cartIO,
            Input.ControllerIO controllerIO, 
            PPU.PpuCpuMemoryMapper ppuMemoryMapper, 
            Sound.SoundController soundController )
        {
            if (cartIO == null)
            {
                throw new ArgumentNullException("cartIO", "The cart io handler cannot be null !");
            }
            if (controllerIO == null)
            {
                throw new ArgumentNullException("controllerIO", "The controller io handler cannot be null !");
            }
            if (ppuMemoryMapper == null)
            {
                throw new ArgumentNullException("ppuMemoryMapper", "The ppu memory mapper cannot be null!");
            }

            _CartIO = cartIO;
            _ControllerIO = controllerIO;
            _PpuMemoryMapper = ppuMemoryMapper;
            _SoundController = soundController;
        }

        #endregion Constructor


        internal void DoDma(byte value)
        {
            // The address of the 1st byte.
            int _Address = 0x100 * value;
            // Read the temp bytes.
            _MainMemory.Read(_PpuMemoryMapper.SpriteMemory, _Address, 256);
            // Set the has dma to true.  need to add 512 cycles for dma.
            _HasDma = true;
        }


        public bool HasDma
        {
            get
            {
                if (_HasDma)
                {
                    _HasDma = false;
                    return true;
                }
                return false;
            }
        }

        /// <summary>
        /// Resets the memory.
        /// </summary>
        /// <returns></returns>
        protected override MemoryItem[] OnInit()
        {
            // Create the main memory.
            _MainMemory = 
                new MemoryBankStream(NesMemoryConstants.MainMemoryByteSize,"Main memory");

            // The internal buffer.
            _MainMemoryBuffer = _MainMemory.InternalBuffer;
            
            // The memory items.
            MemoryItem[] _MemoryItems = new MemoryItem[8];

            // Add the main memory and its for maps.
            _MemoryItems[0] = new MemoryItem(
                _MainMemory,
                new MemoryBlock(
                    NesMemoryConstants.MainMemoryStart,
                    NesMemoryConstants.MainMemoryByteSize
                ),
                new MemoryBlock(
                    NesMemoryConstants.MainMemoryMirrorTwoStart,
                    NesMemoryConstants.MainMemoryByteSize
                    ),
                new MemoryBlock(
                    NesMemoryConstants.MainMemoryMirrorThreeStart,
                    NesMemoryConstants.MainMemoryByteSize
                    ),
                new MemoryBlock(
                    NesMemoryConstants.MainMemoryMirrorFourStart,
                    NesMemoryConstants.MainMemoryByteSize
                    )
                );

            // The ppu io registers.
            _MemoryItems[1] = new MemoryItem(
                _PpuMemoryMapper,
                new MemoryBlock(
                    NesMemoryConstants.IORegistersStart,
                    NesMemoryConstants.IORegistersStart),
                new MemoryBlock(
                    NesMemoryConstants.SpriteDMARegister,
                    1,
                    0)
                );

            // The sound registers.
            _MemoryItems[2] = new MemoryItem(
                _SoundController,
                new MemoryBlock(
                    NesMemoryConstants.SoundIOStart,
                    20
                    ),
                new MemoryBlock(
                    NesMemoryConstants.ApuVerticalClockSignalRegister,
                    1)
                );
            
            // The control io registers.
            _MemoryItems[3] = new MemoryItem(
                _ControllerIO,
                new MemoryBlock(
                    NesMemoryConstants.JoyPadOneRegister,
                    2
                )
            );

            // The program bank one.
            _MemoryItems[4] = _CartIO.ProgramMemoryBankOne;

            // The program bank two.
            _MemoryItems[5] = _CartIO.ProgramMemoryBankTwo;

            _SaveRamTemp = new MemoryBankStream(0x2000,"");
            _MemoryItems[6] = new MemoryItem(
                _SaveRamTemp,
                new MemoryBlock(
                0x6000, 0x2000)
            );

            ExpansionTemp = new MemoryBankStream(0x1FE0, "");
            _MemoryItems[7] = new MemoryItem(
    _SaveRamTemp,
    new MemoryBlock(
    0x4020, 0x1FE0)
);
            return _MemoryItems;
        }

        public override void Write(int address, byte data)
        {
            base.Write(address, data);
            //LogWrite(address, data);
        }

        public override byte Read(ushort address)
        {
            byte _Data = base.Read(address);
            //LogRead(address, _Data);
            return _Data;
        }



        public override byte Read(int address)
        {
            byte _Data = base.Read(address);
            LogRead(address, _Data);
            return _Data;
        }


        /// <summary>
        /// Writes the indirect X.
        /// </summary>
        /// <param name="address">The address.</param>
        /// <param name="registerX">The register X.</param>
        /// <param name="data">The data.</param>
        internal void WriteIndirectX(byte address, byte registerX, byte data)
        {
            address += registerX;
            int _MemoryAddress = (_MainMemoryBuffer[address] |
                ((_MainMemoryBuffer[address + 1]) << 8));

            if (address < NesMemoryConstants.MainMemoryMirrorTwoStart)
            {
                _MainMemoryBuffer[_MemoryAddress] = data;
            }
            else if (address < NesMemoryConstants.IORegistersStart)
            {
                _MainMemoryBuffer[_MemoryAddress & 0x800] = data;
            }
            else
            {
                Write(_MemoryAddress, data);
            }
        }

        /// <summary>
        /// Writes the iindirect Y.
        /// </summary>
        /// <param name="address">The address.</param>
        /// <param name="registerY">The register Y.</param>
        /// <param name="data">The data.</param>
        internal void WriteIndirectY(byte address, byte registerY, byte data)
        {
            int _MemoryAddress = (_MainMemoryBuffer[address] | 
                ((_MainMemoryBuffer[address+1]) << 8)) + registerY;

            if ( address < NesMemoryConstants.MainMemoryMirrorTwoStart )
            {
                _MainMemoryBuffer[_MemoryAddress] = data;
            }
            else if ( address < NesMemoryConstants.IORegistersStart )
            {
                _MainMemoryBuffer[_MemoryAddress&0x800] = data;
            }
            else
            {
                Write(_MemoryAddress,data);
            }
        }


        internal byte[] MainMemoryBuffer
        {
            get
            {
                return _MainMemory.InternalBuffer;
            }
        }
    }
}
